#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <atomic>
#include <sched.h>
#include "util.h"

struct list_param
{
    void *buffer;
    size_t iter;
    size_t len;
};

void *reverse_list_pingpong(void* p){
    struct list_param param = *(struct list_param*)p;
    void *init = *(void* volatile*)(param.buffer);
    // printf("begin iter:%ld\n", param.iter);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    for (size_t i = 0; i < param.iter; i++)
    {
        pointer_chasing_reverse_16(param.buffer, param.len/16);
        // printf("iter %ld/%ld\n", i, param.iter);
        // fflush(NULL);
        while (*(void* volatile*)param.buffer != init);
    }
    return NULL;
}

struct single_param
{
    bool volatile *ball;
    size_t iter;
};

void *single_pingpong(void* p){
    struct single_param *param = (struct single_param*)p;
    bool res, target = !*(param->ball);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    for (size_t i = 0; i < param->iter;)
    {
        //*(param->ball) = param->target;
        //while (*(param->ball) == param->target);
        #ifdef __x86_64
        res = target;
        asm volatile(
            "xchgb %0, (%1)"
            :"+r"(res)
            :"r"(param->ball)
        );
        #elif __loongarch64
        asm volatile(
            "amswap.w %0, %2, %1;"
            :"+r"(res)
            :"r"(param->ball),"r"(target)
        );
        #else
        std::atomic_ref<volatile bool> ball(*(param->ball));
        res = ball.exchange(target);
        #endif
        i += res != target;
    }
    while(target == *(param->ball));
    return NULL;
}

union param
{
    struct list_param list;
    struct single_param single;
};


int main(int argc, char** argv) {

    size_t size = 16384;
    bool single = false, huge_page = true;
    FILE *dataf = NULL;

    for(size_t i = 1; i < argc; i++){
        if(!strcmp(argv[i], "-S")) single = true;
        else if(!strcmp(argv[i], "-h")) huge_page = false;
        else if(i == argc - 1){
            dataf = fopen(argv[argc - 1], "w+");
            if(!dataf) printf("dataf error\n");
        }
    }

    int numberOfProcessors = sysconf(_SC_NPROCESSORS_ONLN);
    printf("Number of processors: %d\n", numberOfProcessors);

    pthread_t pongt;
    pthread_attr_t attr;
    union param pingp, pongp;
    pthread_attr_init(&attr);

    void* buffer;
    goto_cpu(0);//alloc buffer at cpu 0
    if(huge_page) huge_page = try_alloc_hp(buffer);
    if(!huge_page) buffer = malloc(size);
    //volatile bool ball = false;
    volatile bool &ball = *(bool*)((uint8_t*)buffer + 0);
    seq_addr addr(buffer, 16384, 64, 0);
    //mask_addr addr(buffer, 0x3fc0, 0x3fc000);
    if(single){
        pingp.single.ball = &ball;
        pongp.single.ball = &ball;
        pongp.single.iter = __SIZE_MAX__;
    } else{
        fill_buf(addr, thrash_order_gen(addr.size()));
        pingp.list.buffer = addr[0];
        pingp.list.len = addr.size();
        pongp.list.buffer = addr[0];
        pongp.list.len = addr.size();
        pongp.list.iter = __SIZE_MAX__;
    }
    for (size_t i = 0; i < numberOfProcessors; i++)
    {
        goto_cpu(i);

        for (size_t j = 0; j < numberOfProcessors; j++)
        {
            if(i==j){
                printf("%05.2lf ", 0.0);
                continue;
            }

            goto_cpu(attr, j);

            struct timespec begin, end;
            double time;

            if(single){
                pingp.single.iter = 256;
                bool sig = ball;
                if(pthread_create(&pongt, &attr, single_pingpong, &pongp)){
                    printf("pong create error\n");
                    exit(1);
                }
                while(ball == sig);
            } else {
                pingp.list.iter = 1;
                void *sig = *(void * volatile*)(addr[0]);
                if(pthread_create(&pongt, &attr, reverse_list_pingpong, &pongp)){
                    printf("pong create error\n");
                    exit(1);
                }
                while(*(void * volatile*)(addr[0]) == sig);//wait for pong reverse the list
            }

            //warmup 
            size_t iter = 0;
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begin);
            do
            {
                iter ++;
                if(single) single_pingpong(&pingp);
                else reverse_list_pingpong(&pingp);
                clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
            } while (((end.tv_sec - begin.tv_sec)*(1e9) + end.tv_nsec - begin.tv_nsec) < 5e8);

            if(single) pingp.single.iter *= iter * 3;
            else pingp.list.iter *= iter * 3;

            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begin);
            if(single) single_pingpong(&pingp);
            else reverse_list_pingpong(&pingp);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);

            if(pthread_cancel(pongt) || pthread_join(pongt, NULL)){
                printf("pong join error");
                exit(1);
            }

            iter = single ? pingp.single.iter * 2 : pingp.list.iter * pingp.list.len * 2;

            time = ((end.tv_sec - begin.tv_sec)*(1e9) + end.tv_nsec - begin.tv_nsec)/iter;
            if(dataf) fprintf(dataf, "%2ld,%2ld,%05.2lf\n", i, j, time);
            printf("%05.2lf ", time);
            fflush(NULL);
        }
        printf("\n");
    }
    

    return 0;
}
